package org.noip.milhous;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import org.noip.milhous.clause.Where;
import org.noip.milhous.clause.Where.OPERATOR;

public class JavaCodeGenerator
{

	public static final String staticBlock =
			" public static String tableName;\npublic static Map<String, String> fieldNameMapping = new HashMap<String, String>();\n\n static\n\t{\n\tfor (FIELDS f : FIELDS.values())\n\t\t	fieldNameMapping.put(f.name(), f.dbField);\n\t tableName = \"%s\";}";

	public static final String fieldText = "public String dbField;\n" + "\tprivate FIELDS(String s)\n\t{\n\t\tdbField = s;\n}\n";

	public static final String methods = "public String getRealTableName(){		return tableName;	}\n public String getJavaToDbMapping(String s)	{return fieldNameMapping.get(s.toUpperCase());	}\n";

	// public static final String methodText =
	// "	public String getJavaToDbMapping(String javaName)\n{\n\t" +
	// "if (javaToDB == null || javaToDB.isEmpty())\n\t{\n\t\t	for (FIELDS f : FIELDS.values())\n\t\t	javaToDB.put(f.name(), f.dbField);\n\t\t}" +
	// "return javaToDB.get(javaName.toUpperCase());\n}\n";

	// public static final String tableNameHead = "\tpublic String getRealTableName()\n\t\t{\n\t\t\treturn ";

	// public static final String tableNameTail = ".class.getSimpleName();\n\t\t}";

	private static List<COLUMNS> columns = new LinkedList<COLUMNS>();

	static JavaSQLConversion conversion = new JavaSQLConversion();

	/**
	 * Add a package to the buffer. We try to not duplicate packages
	 * 
	 * @param packages
	 *           the buffer to append to
	 * @param packageName
	 *           the item to append to the buffer
	 */
	static void addPackage(StringBuffer packages, String packageName)
		{
			if (packages.lastIndexOf(packageName) == -1)
				{
					packages.append("import " + packageName + ";\n");
				}
		}

	public static String getDBFIELD(String table)
		{
			return "\tpublic String getDatabaseFieldName()\n" + "\t{\n" + "\t\treturn \"`" + table + "`.`\" + dbField + \"`\";\n" + "\t}\n";
		}

	/**
	 * This method will take the info from the information schema, and produce a string that represents the java version of the given file
	 * 
	 * @param t
	 *           the table that represents the information schema
	 * @param tableName
	 *           the table name
	 * @param schemaName
	 *           the schema to use
	 * @param packageName
	 *           the package to output
	 * @return the string rep of the class
	 */
	@SuppressWarnings({ "rawtypes" })
	static String getJavaFile(DatabaseTable t, String tableName, String schemaName, String packageName, HashMap<String, KEY_COLUMN_USAGE> tc, HashMap<String, String> realTableNames, Database db)
		{

			StringBuffer result = new StringBuffer();
			StringBuffer packages = new StringBuffer();
			StringBuffer fields = new StringBuffer();
			StringBuffer fieldENUM = new StringBuffer("public enum FIELDS implements org.noip.milhous.FieldName\n\t{\n\t\t");
			String tabIn = "\n\t\t\t";
			StringBuffer toString = new StringBuffer("\n\tpublic String toString()\n\t\t{\n\t\t\t StringBuffer result = new StringBuffer(); ");
			StringBuffer methodList = new StringBuffer();
			if (packageName != null)
				{
					packages.append("package " + packageName + ";\n");
				}
			addPackage(packages, "org.noip.milhous.annotations.dbVar");
			addPackage(packages, "java.util.Map");
			addPackage(packages, "java.util.HashMap");
			result.append("public class " + tableName + " extends DatabaseRow implements CustomFieldMapping, TableNameMapping \n{\n");
			fields.append("\n\tpublic " + tableName + "()\n\t\t{\n\t\t\tsuper();\n \t\t}");
			Where w = new Where(new Where("TABLE_NAME", OPERATOR.EQUAL, "?"), OPERATOR.AND, new Where("TABLE_SCHEMA", OPERATOR.EQUAL, "?"));
			columns = db.select(COLUMNS.class, w, tableName, schemaName);
			for (COLUMNS c : columns)
				{

					fields.append("\t@dbVar\n");
					fields.append("\t");
					if (c.isPrimaryKey())
						{
							addPackage(packages, "org.noip.milhous.annotations.primarykey");
							fields.append("@primarykey\n\t");
						}
					FIELDTYPES theType = JavaCodeGenerator.conversion.getDBType(c.DATA_TYPE, c);
					switch (theType)
						{
						case Date:
							addPackage(packages, "java.sql.Date");
							break;
						case Blob:
							addPackage(packages, "java.sql.Blob");
							addPackage(packages, "org.noip.milhous.annotations.length");
							fields.append("@length(\"" + c.CHARACTER_MAXIMUM_LENGTH + "\")\n\t");
							break;
						case STRING:
							addPackage(packages, "org.noip.milhous.annotations.length");
							fields.append("@length(\"" + c.CHARACTER_MAXIMUM_LENGTH + "\")\n\t");
							break;
						case LONG:
							addPackage(packages, "java.math.BigInteger");
							break;
						case TIMESTAMP:
							addPackage(packages, "java.sql.Timestamp");
							break;
						case DECIMAL:
							addPackage(packages, "java.math.BigDecimal");
							break;
						}
					String type = theType.getJava();
					KEY_COLUMN_USAGE k = tc.get(c.COLUMN_NAME);
					if (k != null && k.getCOLUMN_NAME().equals(c.COLUMN_NAME) && k.getREFERENCED_TABLE_NAME() != null && k.getREFERENCED_COLUMN_NAME() != null)
						{
							String refName = realTableNames.get(k.getREFERENCED_TABLE_NAME().toLowerCase());
							addPackage(packages, "org.noip.milhous.annotations.foriegnKey");
							fields.append("@foriegnKey(table = \"" + refName + "\", column = \"" + k.getREFERENCED_COLUMN_NAME() + "\")\n\t");

						}
					fields.append("private " + type + " ");
					String orgColName = new String(c.COLUMN_NAME);
					if (c.COLUMN_NAME.contains(" "))
						{
							c.COLUMN_NAME = c.COLUMN_NAME.replace(" ", "_");
						}
					else if (c.COLUMN_NAME.contains("#"))
						{
							c.COLUMN_NAME = c.COLUMN_NAME.replace("#", "_");
						}
					fields.append(c.COLUMN_NAME + "= " + c.getDefault(theType));
					fieldENUM.append(c.COLUMN_NAME.toUpperCase() + "(\"" + orgColName + "\"),");
					fields.append(";\n\n");
					toString.append(tabIn + "result.append(" + "\"" + c.COLUMN_NAME + ":\"");
					String name = getGetterMethodName(c.COLUMN_NAME);
					if (theType.equals(FIELDTYPES.Date))
						{
							toString.append(" + dateToString(" + name + ")  ");
						}
					else
						{
							toString.append(" + " + name + " ");
						}
					toString.append("+\"\\n\"");
					toString.append(");");
					addMethods(theType.getJava(), c.COLUMN_NAME, methodList);

				}
			addPackage(packages, "org.noip.milhous.DatabaseRow");
			toString.append(tabIn + "return result.toString();\n\t\t}");
			fieldENUM.deleteCharAt(fieldENUM.length() - 1);
			fieldENUM.append(";\n" + fieldText + JavaCodeGenerator.getDBFIELD(tableName));
			fieldENUM.append("\n\t}\n\n");
			// addPackage(packages, "org.noip.milhous.annotations.transientVar");
			addPackage(packages, "org.noip.milhous.CustomFieldMapping");
			addPackage(packages, "org.noip.milhous.TableNameMapping");
			// result.append("@transientVar\n\t");
			// result.append("public Map<String, String> javaToDB = new HashMap<String, String>();\n");
			result.append(String.format(staticBlock, tableName) + "\n");

			result.append(fieldENUM);
			result.append(fields);
			result.append(methodList.toString());
			// result.append(methodText);
			// result.append(JavaCodeGenerator.tableNameHead + tableName + JavaCodeGenerator.tableNameTail);
			result.append(toString.toString());
			result.append(methods);
			result.append("\n}");
			return packages.toString() + result.toString();
		}

	/**
	 * Add the methods for a particular variable
	 * 
	 * @param retrunType
	 *           the return type of the variable
	 * @param name
	 *           the name of the variable
	 * @param methodBuffer
	 *           the buffer to append to
	 */
	public static void addMethods(String retrunType, String name, StringBuffer methodBuffer)
		{
			// Get method
			methodBuffer.append("\tpublic " + retrunType + getGetterMethodName(name) + "\n\t\t{");
			methodBuffer.append("\n\t\t\treturn " + name + ";\n");
			methodBuffer.append("\t\t}\n");
			// Set method
			methodBuffer.append("\tpublic void set" + name.toUpperCase() + "(" + retrunType + " " + name.toLowerCase() + ")\n\t\t{");
			methodBuffer.append("\n\t\t\tthis." + name + " = " + name.toLowerCase() + ";\n");
			methodBuffer.append("\t\t}\n");
		}

	public static String getGetterMethodName(String name)
		{
			return " get" + name.toUpperCase() + "()";
		}

	/**
	 * Run the Java creator on a bunch of java classes
	 * 
	 * @param fileList
	 *           we assume that the package name is last and the table name is second to last the rest are tables
	 * @throws ClassNotFoundException
	 * @throws SQLException
	 * @throws FileNotFoundException
	 */

	public static void main(String[] fileList)
		{
			try
				{
					if (fileList.length == 2 || fileList.length == 3)
						{
							bbb(fileList);
						}
					else
						{
							System.err.println("Arg 0 is the schema, Arg 1 is the package, Arg 3(optional) is the directory ");
						}
				}
			catch (Exception e)
				{
				}
		}

	public static void bbb(String[] aa) throws ClassNotFoundException, SQLException, FileNotFoundException
		{
			SQL infoSchema = new SQL("localhost", "3306", "information_schema", "azirt", "azirt");
			Database db = new Database(infoSchema);
			String schema = aa[0];
			List<TABLES> tableLis2t = db.select(TABLES.class, new Where(TABLES.FIELDS.TABLE_SCHEMA.getDatabaseFieldName(), OPERATOR.EQUAL, "?"), schema);
			String packageName = aa[1];
			String Directory = aa[2];
			aaa(packageName, schema, tableLis2t, Directory);
		}

	public static void aaa(String packageName, String schemaName, List<TABLES> tables, String dir) throws FileNotFoundException
		{
			SQL infoSchema = new SQL("localhost", "3306", "information_schema", "azirt", "azirt");
			Database db = new Database(infoSchema);
			aaa2(packageName, schemaName, tables, dir, db);
		}

	public static void aaa2(String packageName, String schemaName, List<TABLES> tables, String dir, Database db) throws FileNotFoundException
		{

			// SQL infoSchema = new SQL("pogo", "3306", "information_schema", "fax_user", "WhoamI");
			DatabaseTable<COLUMNS> t = db.getTable(new COLUMNS());
			DatabaseTable<KEY_COLUMN_USAGE> Contsts = db.getTable(KEY_COLUMN_USAGE.class);
			DatabaseTable<TABLES> tableList = db.getTable(TABLES.class);
			FileOutputStream out = null;
			PrintStream p = null;
			List<TABLES> tableLis2t = tableList.select();
			HashMap<String, String> realTableNames = new HashMap<String, String>();
			HashMap<String, KEY_COLUMN_USAGE> constraintMap = new HashMap<String, KEY_COLUMN_USAGE>();
			Where w1 = new Where(KEY_COLUMN_USAGE.FIELDS.TABLE_SCHEMA, OPERATOR.EQUAL, "?");
			Where w2 = new Where(KEY_COLUMN_USAGE.FIELDS.CONSTRAINT_NAME, OPERATOR.NOTEQUAL, "?");
			Where w4 = new Where(KEY_COLUMN_USAGE.FIELDS.TABLE_NAME, OPERATOR.EQUAL, "?");
			Where w3 = new Where(new Where(w1, OPERATOR.AND, w2), OPERATOR.AND, w4);
			if (dir.charAt(dir.length() - 1) != '/')
				{
					dir = dir + "/";
				}
			for (TABLES tabl : tableLis2t)
				{
					realTableNames.put(tabl.getTABLE_NAME().toLowerCase(), tabl.getTABLE_NAME());
				}
			for (TABLES singleTable : tables)
				{
					String tname = singleTable.getTABLE_NAME();
					constraintMap.clear();
					List<KEY_COLUMN_USAGE> constraints = Contsts.select(w3, schemaName, "PRIMARY", tname);
					for (KEY_COLUMN_USAGE k : constraints)
						{
							constraintMap.put(k.getCOLUMN_NAME(), k);
						}
					String fileData = getJavaFile(t, tname, schemaName, packageName, constraintMap, realTableNames, db);
					// String fileData = getJavaFile(t, fileList[i], tableName, packageName, null);
					System.out.println(tname);
					out = new FileOutputStream(dir + tname + ".java");
					p = new PrintStream(out);
					p.println(fileData);
					p.close();
				}
		}

	public static void generateAllTablesForSchema(String baseDirectory, String packageName, String schemaName, Database database) throws FileNotFoundException
		{

			List<TABLES> tables = database.select(TABLES.class, new Where(TABLES.FIELDS.TABLE_SCHEMA.getDatabaseFieldName(), OPERATOR.EQUAL, "?"), schemaName);
			DatabaseTable<COLUMNS> t = database.getTable(new COLUMNS());
			DatabaseTable<KEY_COLUMN_USAGE> Contsts = database.getTable(KEY_COLUMN_USAGE.class);
			DatabaseTable<TABLES> tableList = database.getTable(TABLES.class);
			FileOutputStream out = null;
			PrintStream p = null;
			List<TABLES> tableLis2t = tableList.select();
			HashMap<String, String> realTableNames = new HashMap<String, String>();
			HashMap<String, KEY_COLUMN_USAGE> constraintMap = new HashMap<String, KEY_COLUMN_USAGE>();
			Where w1 = new Where(KEY_COLUMN_USAGE.FIELDS.TABLE_SCHEMA, OPERATOR.EQUAL, "?");
			Where w2 = new Where(KEY_COLUMN_USAGE.FIELDS.CONSTRAINT_NAME, OPERATOR.NOTEQUAL, "?");
			Where w4 = new Where(KEY_COLUMN_USAGE.FIELDS.TABLE_NAME, OPERATOR.EQUAL, "?");
			Where w3 = new Where(new Where(w1, OPERATOR.AND, w2), OPERATOR.AND, w4);

			if (baseDirectory.charAt(baseDirectory.length() - 1) != '/')
				{
					baseDirectory = baseDirectory + "/";
				}
			baseDirectory += packageName.replace(".", "/") + "/";
			for (TABLES tabl : tableLis2t)
				{
					realTableNames.put(tabl.getTABLE_NAME().toLowerCase(), tabl.getTABLE_NAME());
				}
			for (TABLES singleTable : tables)
				{
					String tname = singleTable.getTABLE_NAME();
					constraintMap.clear();
					List<KEY_COLUMN_USAGE> constraints = Contsts.select(w3, schemaName, "PRIMARY", tname);
					for (KEY_COLUMN_USAGE k : constraints)
						{
							constraintMap.put(k.getCOLUMN_NAME(), k);
						}
					String fileData = getJavaFile(t, tname, schemaName, packageName, constraintMap, realTableNames, database);
					// String fileData = getJavaFile(t, fileList[i], tableName, packageName, null);
					System.out.println(tname);
					out = new FileOutputStream(baseDirectory + tname + ".java");
					p = new PrintStream(out);
					p.println(fileData);
					p.close();
				}
		}

	public static void generateAllTablesForSchemaNoDep(String baseDirectory, String packageName, String schemaName, Database database) throws FileNotFoundException
		{

			List<TABLES> tables = database.select(TABLES.class, new Where(TABLES.FIELDS.TABLE_SCHEMA.getDatabaseFieldName(), OPERATOR.EQUAL, "?"), schemaName);
			DatabaseTable<COLUMNS> t = database.getTable(new COLUMNS());
			DatabaseTable<KEY_COLUMN_USAGE> Contsts = database.getTable(KEY_COLUMN_USAGE.class);
			DatabaseTable<TABLES> tableList = database.getTable(TABLES.class);
			FileOutputStream out = null;
			PrintStream p = null;
			List<TABLES> tableLis2t = tableList.select();
			HashMap<String, String> realTableNames = new HashMap<String, String>();
			HashMap<String, KEY_COLUMN_USAGE> constraintMap = new HashMap<String, KEY_COLUMN_USAGE>();
			Where w1 = new Where(KEY_COLUMN_USAGE.FIELDS.TABLE_SCHEMA, OPERATOR.EQUAL, "?");
			Where w2 = new Where(KEY_COLUMN_USAGE.FIELDS.CONSTRAINT_NAME, OPERATOR.NOTEQUAL, "?");
			Where w4 = new Where(KEY_COLUMN_USAGE.FIELDS.TABLE_NAME, OPERATOR.EQUAL, "?");
			Where w3 = new Where(new Where(w1, OPERATOR.AND, w2), OPERATOR.AND, w4);

			if (baseDirectory.charAt(baseDirectory.length() - 1) != '/')
				{
					baseDirectory = baseDirectory + "/";
				}
			baseDirectory += packageName.replace(".", "/") + "/";
			for (TABLES tabl : tableLis2t)
				{
					realTableNames.put(tabl.getTABLE_NAME().toLowerCase(), tabl.getTABLE_NAME());
				}
			for (TABLES singleTable : tables)
				{
					String tname = singleTable.getTABLE_NAME();
					constraintMap.clear();
					List<KEY_COLUMN_USAGE> constraints = Contsts.select(w3, schemaName, "PRIMARY", tname);
					for (KEY_COLUMN_USAGE k : constraints)
						{
							constraintMap.put(k.getCOLUMN_NAME(), k);
						}
					String fileData = getJavaFile(t, tname, schemaName, packageName, constraintMap, realTableNames, database);
					// String fileData = getJavaFile(t, fileList[i], tableName, packageName, null);
					System.out.println(tname);
					out = new FileOutputStream(baseDirectory + tname + ".java");
					p = new PrintStream(out);
					p.println(fileData);
					p.close();
				}
		}
}
